/**************************************************************************//**
 * @file     hid_driver.c
 * @brief    USB Host HID driver
 *
 * SPDX-License-Identifier: Apache-2.0
 * @copyright (C) 2023 Nuvoton Technology Corp. All rights reserved.
*****************************************************************************/

#include <stdio.h>
#include <string.h>

#include "usbh_lib.h"
#include "usbh_hid.h"

/** @addtogroup LIBRARY Library
  @{
*/

/** @addtogroup USBH_Library USB Host Library
  @{
*/

/** @addtogroup USBH_EXPORTED_FUNCTIONS USB Host Exported Functions
  @{
*/

/// @cond HIDDEN_SYMBOLS

extern int hid_parse_report_descriptor(HID_DEV_T *hdev, IFACE_T *iface);

static HID_DEV_T *g_hdev_list = NULL;

static HID_DEV_T *alloc_hid_device(void)
{
	return usbh_alloc_mem(sizeof(HID_DEV_T));
}

void  free_hid_device(HID_DEV_T *hid_dev)
{
	usbh_free_mem(hid_dev, sizeof(HID_DEV_T));
}

static int hid_probe(IFACE_T *iface)
{
	UDEV_T       *udev = iface->udev;
	ALT_IFACE_T  *aif = iface->aif;
	DESC_IF_T    *ifd;
	EP_INFO_T    *ep;
	HID_DEV_T    *hdev, *p;
	int          i;

	ifd = aif->ifd;

	/* Is this interface HID class? */
	if (ifd->bInterfaceClass != USB_CLASS_HID)
		return USBH_ERR_NOT_MATCHED;

	HID_DBGMSG("hid_probe - device (vid=0x%x, pid=0x%x), interface %d, subclass 0x%x, protocol 0x%x.\n",
			   udev->descriptor.idVendor, udev->descriptor.idProduct, ifd->bInterfaceNumber,
			   ifd->bInterfaceSubClass, ifd->bInterfaceProtocol);

	/*
	 *  Try to find any interrupt endpoints
	 */
	for (i = 0; i < aif->ifd->bNumEndpoints; i++)
	{
		if ((aif->ep[i].bmAttributes & EP_ATTR_TT_MASK) == EP_ATTR_TT_INT)
		{
			ep = &aif->ep[i];
			break;
		}
	}

	if (ep == NULL)
		return USBH_ERR_NOT_MATCHED;   // No endpoints, Ignore this interface

	hdev = alloc_hid_device();
	if (hdev == NULL)
		return HID_RET_OUT_OF_MEMORY;

	hdev->iface = iface;
	hdev->idVendor  = udev->descriptor.idVendor;
	hdev->idProduct = udev->descriptor.idProduct;
	hdev->bSubClassCode = ifd->bInterfaceSubClass;
	hdev->bProtocolCode = ifd->bInterfaceProtocol;
	hdev->next = NULL;
	iface->context = (void *)hdev;

	hid_parse_report_descriptor(hdev, iface);

	/*
	 *  Chaining newly found HID device to end of HID device list.
	 */
	if (g_hdev_list == NULL)
		g_hdev_list = hdev;
	else
	{
		for (p = g_hdev_list; p->next != NULL; p = p->next)
			;
		p->next = hdev;
	}
	HID_DBGMSG("usbhid_probe OK. hdev=0x%x\n", ptr_to_u32(hdev));
	return 0;
}


static void  hid_disconnect(IFACE_T *iface)
{
	HID_DEV_T   *hdev, *p;
	UTR_T       *utr;
	RP_INFO_T   *rp, *next_rp;
	int         i;

	hdev = (HID_DEV_T *)(iface->context);

	for (i = 0; i < iface->aif->ifd->bNumEndpoints; i++)
	{
		iface->udev->hc_driver->quit_xfer(NULL, &(iface->aif->ep[i]));
	}

	/*
	 *  Abort all UTR of this HID device (interface)
	 */
	for (i = 0; i < CONFIG_HID_DEV_MAX_PIPE; i++)
	{
		utr = hdev->utr_list[i];
		if (utr != NULL)
		{
			usbh_quit_utr(utr);             /* Quit the UTR                               */
			usbh_free_mem(utr->buff, utr->ep->wMaxPacketSize);
			free_utr(utr);
		}
	}

	if (hdev->rpd.utr_led != NULL)
	{
		usbh_quit_utr(hdev->rpd.utr_led);   /* Quit the UTR                               */
		free_utr(hdev->rpd.utr_led);
	}

	if (hdev->rpd.report != NULL)
	{
		for (rp = hdev->rpd.report; rp != NULL; )
		{
			next_rp = rp->next;
			usbh_free_mem(rp, sizeof(RP_INFO_T));
			rp = next_rp;
		}
	}

	/*
	 *  remove it from HID device list
	 */
	if (hdev == g_hdev_list)
	{
		g_hdev_list = g_hdev_list->next;
	}
	else
	{
		for (p = g_hdev_list; p != NULL; p = p->next)
		{
			if (p->next == hdev)
			{
				p->next = hdev->next;
				break;
			}
		}
	}
	HID_DBGMSG("hid_disconnect - device (vid=0x%x, pid=0x%x), interface %d.\n",
				hdev->idVendor, hdev->idProduct, iface->if_num);
	free_hid_device(hdev);
}


UDEV_DRV_T  hid_driver =
{
	hid_probe,
	hid_disconnect,
	NULL,                       /* suspend */
	NULL,                       /* resume */
};

/// @endcond /* HIDDEN_SYMBOLS */

/**
  * @brief    Initialize USB Host HID driver.
  */
void usbh_hid_init(void)
{
	g_hdev_list = NULL;
	usbh_register_driver(&hid_driver);
}

/**
 *  @brief   Get a list of currently connected USB Hid devices.
 *  @return  A list HID_DEV_T pointer reference to connected HID devices.
 *  @retval  NULL       There's no HID device found.
 *  @retval  Otherwise  A list of connected HID devices.
 *
 *  The HID devices are chained by the "next" member of HID_DEV_T.
 */
HID_DEV_T * usbh_hid_get_device_list(void)
{
	return g_hdev_list;
}

/*! @}*/ /* end of group USBH_EXPORTED_FUNCTIONS */

/*! @}*/ /* end of group USBH_Library */

/*! @}*/ /* end of group Library */
